package com.garvin.gateway.system;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import sun.security.rsa.RSAPublicKeyImpl;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @author : garvin
 * @version V1.0
 * @date : 2020年10月19日 16:08
 */
@Slf4j
@Component
public class RouteUrlTokenFilter implements GlobalFilter, Ordered {

    private Map<String, Object> tokenSignerMap;

    private String authTokenSignerUrl;

    private RestTemplate restTemplate;

    public RouteUrlTokenFilter(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String applicationPath = request.getURI().getPath();

        // 请求认证中心，不过滤StripPrefix
        String authenticationCenter = "/authenticationCenter";
        if (applicationPath.startsWith(authenticationCenter)) {
            return chain.filter(exchange);
        }

        // 判断请求头 是否包含 token
        boolean authorization = request.getHeaders().containsKey("Authorization");
        if (!authorization) {
            log.info("token is empty...");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 校验 token
        String token = getToken(request);
        Object keyObject = getPublicKey();
        Map<String, Object> validate = tokenValidate(token, keyObject);
        Object message;
        String valid = "valid";
        if (validate.containsKey(valid) && !(boolean) validate.get(valid)) {
            message = "token 已经过期";
        } else {
            message = Optional.ofNullable(validate.get("SignatureException")).orElse(validate.get("Exception"));
        }
        if (Objects.nonNull(message)) {
            DataBuffer wrap = response.bufferFactory().wrap(message.toString().getBytes());
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.writeWith(Mono.just(wrap));
        }

        return chain.filter(exchange);
    }

    private String getToken(ServerHttpRequest request) {
        String bearer = "bearer";
        String token = request.getHeaders().getFirst("Authorization");
        if (token.startsWith(bearer)) {
            token = token.replaceFirst(bearer, "").trim();
        }
        return token;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Map<String, Object> tokenValidate(String token, Object keyObject) {
        Map<String, Object> result = new HashMap<>(8);
        try {
            byte[] decodeKey = Base64Utils.decodeFromString(keyObject.toString());
            RSAPublicKeyImpl key = new RSAPublicKeyImpl(decodeKey);
            Jws<Claims> jws = Jwts.parser().setSigningKey(key).parseClaimsJws(token);
            Assert.notNull(jws, "token parse , jws must not be null");

            // token 可以被认证中心的秘钥解析，验证成功
            Date expiration = jws.getBody().getExpiration();
            result.put("valid", expiration.after(new Date()));
        } catch (Exception e) {
            if (e instanceof SignatureException) {
                log.warn("token is invalid : {}", keyObject.toString());
                result.put("SignatureException", "Token 不合法");
            } else {
                log.error("token validate found exception", e);
                result.put("Exception", "Token 验证异常");
            }
        }
        return result;
    }

    private Object getPublicKey() {
        Object keyObject = tokenSignerMap.get("default");
        Assert.notNull(keyObject, "not found public key [default] from map");
        return keyObject;
    }

    @PostConstruct
    public void loadTokenSigner() {
        // 从认证中心，加载验证Token的秘钥
        authTokenSignerUrl = "http://localhost:7070/rsa/key";
        tokenSignerMap = restTemplate.getForObject(authTokenSignerUrl, Map.class);
    }

}
